//
// Created by Soar Qin on 2017/8/22.
// Copyright (c) 2017 Soar Qin. All rights reserved.
//

#include "common.hh"

#include "tcp_endpoint.hh"
#include "loop.hh"
#include "ip_addr.hh"

#include "core/async/private/tcp_endpoint_private.hh"
#include "private/loop_private.hh"

#ifdef _WIN32
#define SHUT_RDWR SD_BOTH
#endif

namespace core::async {

static void ioStreamReadcb(struct bufferevent *bev, void *ctx) {
    (void) bev;
    auto *ios = static_cast<TCPEndpoint*>(ctx);
    ios->onRead();
}

static void ioStreamWritecb(struct bufferevent *bev, void *ctx) {
    (void) bev;
    auto *ios = static_cast<TCPEndpoint*>(ctx);
    ios->onWrite();
}

static void ioStreamEventcb(struct bufferevent *bev, short what, void *ctx) {
    if ((what & (BEV_EVENT_ERROR | BEV_EVENT_EOF)) != 0) {
        auto *ios = static_cast<TCPEndpoint *>(ctx);
        ios->onClosed();
        if (ios->isDeleteOnClose())
            delete ios;
        return;
    }
    if ((what & BEV_EVENT_CONNECTED) != 0) {
        auto *ios = static_cast<TCPEndpoint *>(ctx);
        bufferevent_enable(bev, EV_READ | EV_WRITE);
        ios->onConnected();
        return;
    }
}

TCPEndpoint::TCPEndpoint(Loop &loop): IoBase(loop), context_(new TCPEndpointContext) {
}

TCPEndpoint::~TCPEndpoint() {
    if (context_->bev != nullptr)
        bufferevent_free(context_->bev);
    delete context_;
}

bool TCPEndpoint::bind(int fd) {
    if (context_->bev != nullptr) return false;
    context_->bev = bufferevent_socket_new(loop_.getContext()->evbase, fd, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
    if (context_->bev == nullptr) return false;
    bufferevent_setcb(context_->bev, ioStreamReadcb, ioStreamWritecb, ioStreamEventcb, this);
    if (fd >= 0)
        bufferevent_enable(context_->bev, EV_READ | EV_WRITE);
    return true;
}

bool TCPEndpoint::connect(const IPAddr &addr) {
    if (!addr.ok()) return false;
    if (!bind(-1)) return false;
    int socklen;
    const auto *sockaddr = addr.getAddr(socklen);
    return bufferevent_socket_connect(context_->bev, sockaddr, socklen) == 0;
}

bool TCPEndpoint::connect(const std::string &address) {
    return connect(IPAddr(address));
}

bool TCPEndpoint::connect(const std::string &ip, uint16_t port) {
    return connect(IPAddr(ip, port));
}

size_t TCPEndpoint::read(void *buf, size_t size) {
    if (context_->bev == nullptr) return 0;
    return bufferevent_read(context_->bev, buf, size);
}

int TCPEndpoint::write(const void *buf, size_t size) {
    if (context_->bev == nullptr) return -1;
    return bufferevent_write(context_->bev, buf, size);
}

int TCPEndpoint::writeTo(TCPEndpoint *to) {
    if (context_->bev == nullptr || to->context_->bev == nullptr) return -1;
    bufferevent_read_buffer(context_->bev, bufferevent_get_output(to->context_->bev));
    return 0;
}

void TCPEndpoint::shutdown() {
    if (context_->bev == nullptr) return;
    ::shutdown(bufferevent_getfd(context_->bev), SHUT_RDWR);
}

void TCPEndpoint::onClosed() {
    if (context_->bev != nullptr) {
        bufferevent_free(context_->bev);
        context_->bev = nullptr;
    }
}

void TCPEndpoint::enable(uint32_t op) {
    auto ev = bufferevent_get_enabled(context_->bev);
    if ((ev & EV_READ) != 0) {
        if ((op & READ) == 0) {
            bufferevent_disable(context_->bev, EV_READ);
        }
    } else {
        if ((op & READ) != 0) {
            bufferevent_enable(context_->bev, EV_READ);
        }
    }
    if ((ev & EV_WRITE) != 0) {
        if ((op & WRITE) == 0) {
            bufferevent_disable(context_->bev, EV_WRITE);
        }
    } else {
        if ((op & WRITE) != 0) {
            bufferevent_enable(context_->bev, EV_WRITE);
        }
    }
}

}
